#! /usr/bin/env python
# coding=utf-8
#================================================================
#   Copyright (C) 2019 * Ltd. All rights reserved.
#
#   Editor      : VIM
#   File name   : yolov3.py
#   Author      : YunYang1994
#   Created date: 2019-02-28 10:47:03
#   Description :
#
#================================================================

import numpy as np
import tensorflow as tf
import core.utils as utils
import core.common as common
import core.backbone as backbone
from core.config import cfg


class YOLOV3(object):
    """Implement tensoflow yolov3 here"""
    def __init__(self, input_data, trainable):

        self.trainable        = trainable
        self.classes          = utils.read_class_names(cfg.YOLO.CLASSES)
        self.num_class        = len(self.classes)
        self.strides          = np.array(cfg.YOLO.STRIDES)

        # 1.25,1.625, 2.0,3.75, 4.125,2.875,
        # 1.875,3.8125, 3.875,2.8125, 3.6875,7.4375,
        # 3.625,2.8125, 4.875,6.1875, 11.65625,10.1875
        self.anchors          = utils.get_anchors(cfg.YOLO.ANCHORS)
        self.anchor_per_scale = cfg.YOLO.ANCHOR_PER_SCALE
        self.iou_loss_thresh  = cfg.YOLO.IOU_LOSS_THRESH
        self.upsample_method  = cfg.YOLO.UPSAMPLE_METHOD

        try:
            self.conv_lbbox, self.conv_mbbox, self.conv_sbbox = self.__build_nework(input_data)
        except:
            raise NotImplementedError("Can not build up yolov3 network!")

        with tf.variable_scope('pred_sbbox'):
            self.pred_sbbox = self.decode(self.conv_sbbox, self.anchors[0], self.strides[0])

        with tf.variable_scope('pred_mbbox'):
            self.pred_mbbox = self.decode(self.conv_mbbox, self.anchors[1], self.strides[1])

        with tf.variable_scope('pred_lbbox'):
            self.pred_lbbox = self.decode(self.conv_lbbox, self.anchors[2], self.strides[2])

    def __build_nework(self, input_data):

        route_1, route_2, input_data = backbone.darknet53(input_data, self.trainable)

        input_data = common.convolutional(input_data, (1, 1, 1024,  512), self.trainable, 'conv52')
        input_data = common.convolutional(input_data, (3, 3,  512, 1024), self.trainable, 'conv53')
        input_data = common.convolutional(input_data, (1, 1, 1024,  512), self.trainable, 'conv54')
        input_data = common.convolutional(input_data, (3, 3,  512, 1024), self.trainable, 'conv55')
        input_data = common.convolutional(input_data, (1, 1, 1024,  512), self.trainable, 'conv56')

        conv_lobj_branch = common.convolutional(input_data, (3, 3, 512, 1024), self.trainable, name='conv_lobj_branch')
        conv_lbbox = common.convolutional(conv_lobj_branch, (1, 1, 1024, 3*(self.num_class + 5)),
                                          trainable=self.trainable, name='conv_lbbox', activate=False, bn=False)

        input_data = common.convolutional(input_data, (1, 1,  512,  256), self.trainable, 'conv57')
        input_data = common.upsample(input_data, name='upsample0', method=self.upsample_method)

        with tf.variable_scope('route_1'):
            input_data = tf.concat([input_data, route_2], axis=-1)

        input_data = common.convolutional(input_data, (1, 1, 768, 256), self.trainable, 'conv58')
        input_data = common.convolutional(input_data, (3, 3, 256, 512), self.trainable, 'conv59')
        input_data = common.convolutional(input_data, (1, 1, 512, 256), self.trainable, 'conv60')
        input_data = common.convolutional(input_data, (3, 3, 256, 512), self.trainable, 'conv61')
        input_data = common.convolutional(input_data, (1, 1, 512, 256), self.trainable, 'conv62')

        conv_mobj_branch = common.convolutional(input_data, (3, 3, 256, 512),  self.trainable, name='conv_mobj_branch' )
        conv_mbbox = common.convolutional(conv_mobj_branch, (1, 1, 512, 3*(self.num_class + 5)),
                                          trainable=self.trainable, name='conv_mbbox', activate=False, bn=False)

        input_data = common.convolutional(input_data, (1, 1, 256, 128), self.trainable, 'conv63')
        input_data = common.upsample(input_data, name='upsample1', method=self.upsample_method)

        with tf.variable_scope('route_2'):
            input_data = tf.concat([input_data, route_1], axis=-1)

        input_data = common.convolutional(input_data, (1, 1, 384, 128), self.trainable, 'conv64')
        input_data = common.convolutional(input_data, (3, 3, 128, 256), self.trainable, 'conv65')
        input_data = common.convolutional(input_data, (1, 1, 256, 128), self.trainable, 'conv66')
        input_data = common.convolutional(input_data, (3, 3, 128, 256), self.trainable, 'conv67')
        input_data = common.convolutional(input_data, (1, 1, 256, 128), self.trainable, 'conv68')

        conv_sobj_branch = common.convolutional(input_data, (3, 3, 128, 256), self.trainable, name='conv_sobj_branch')
        conv_sbbox = common.convolutional(conv_sobj_branch, (1, 1, 256, 3*(self.num_class + 5)),
                                          trainable=self.trainable, name='conv_sbbox', activate=False, bn=False)

        return conv_lbbox, conv_mbbox, conv_sbbox

    def decode(self, conv_output, anchors, stride):
        """
        return tensor of shape [batch_size, output_size, output_size, anchor_per_scale, 5 + num_classes]
               contains (x, y, w, h, score, probability)
        """

        conv_shape       = tf.shape(conv_output)
        batch_size       = conv_shape[0]
        output_size      = conv_shape[1]
        anchor_per_scale = len(anchors)

        conv_output = tf.reshape(conv_output, (batch_size, output_size, output_size, anchor_per_scale, 5 + self.num_class))

        conv_raw_dxdy = conv_output[:, :, :, :, 0:2]
        conv_raw_dwdh = conv_output[:, :, :, :, 2:4]
        conv_raw_conf = conv_output[:, :, :, :, 4:5]
        conv_raw_prob = conv_output[:, :, :, :, 5: ]

        # 这里的output_size 是yolov3 网络最后一层的feature map 的大小
        '''
        for example:
        output_size = 20;
        y = tf.tile(tf.range(output_size, dtype=tf.int32)[:, tf.newaxis], [1, output_size])
        with tf.Session() as sess:
            output = sess.run(y);
            print(output.shape);
            print(output)
        
        output:
        (20, 20)
        [[ 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0]
         [ 1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1  1]
         [ 2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2  2]
         [ 3  3  3  3  3  3  3  3  3  3  3  3  3  3  3  3  3  3  3  3]
         [ 4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4  4]
         [ 5  5  5  5  5  5  5  5  5  5  5  5  5  5  5  5  5  5  5  5]
         [ 6  6  6  6  6  6  6  6  6  6  6  6  6  6  6  6  6  6  6  6]
         [ 7  7  7  7  7  7  7  7  7  7  7  7  7  7  7  7  7  7  7  7]
         [ 8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8  8]
         [ 9  9  9  9  9  9  9  9  9  9  9  9  9  9  9  9  9  9  9  9]
         [10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10]
         [11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11]
         [12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12]
         [13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13]
         [14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14]
         [15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15]
         [16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16]
         [17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17]
         [18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18]
         [19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19]]
        '''
        y = tf.tile(tf.range(output_size, dtype=tf.int32)[:, tf.newaxis], [1, output_size]);
        '''
        for example:
        x = tf.tile(tf.range(output_size, dtype=tf.int32)[tf.newaxis, :], [output_size, 1]);
        
        output:
        [[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]
         [ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]]
        '''
        # 可以看出这里的生成矩阵的做法是有方向性的, y和x 的增大方向和笛卡尔坐标有点类似
        # 不过要强调的是在数字图像处理中, 左上角才是坐标的原点,
        x = tf.tile(tf.range(output_size, dtype=tf.int32)[tf.newaxis, :], [output_size, 1]);
        # 这里因为增加了一个维度, 这个维度正好是通道数, 在参数axis = -1的情况下把2个坐标分类的矩阵在通道数上叠加;
        xy_grid = tf.concat([x[:, :, tf.newaxis], y[:, :, tf.newaxis]], axis=-1);
        '''
        for example:
        batch_size = 5;
        anchor_per_scale = 3;
        xy_grid = tf.tile(xy_grid[tf.newaxis, :, :, tf.newaxis, :], [batch_size, 1, 1, anchor_per_scale, 1]);
        with tf.Session() as sess:
            xy_output = sess.run(xy_grid);
            print(xy_output.shape);
            
        output:
        (5, 20, 20, 3, 2)
        '''
        # 可以看出这里的结果看是对应的维度相乘的结果
        xy_grid = tf.tile(xy_grid[tf.newaxis, :, :, tf.newaxis, :], [batch_size, 1, 1, anchor_per_scale, 1]);
        # 把数据的类型从整型变成了浮点型
        xy_grid = tf.cast(xy_grid, tf.float32);
        # 从这一步可以看出前面对 xy_grid 的数据处理了那么多次就是为了把形状以及每一个维度所对应的物理意义和conv 直接算出来的一致;
        # xy_grid 中shape[0] 是 batch, shape[1]和shape[2] 是最终一个尺度下的feature map;
        # xy_grid 中的每一个点都是一个像素点的位置,
        # 我认为这里采用 + xy_grid 这个矩阵的意义在于按照论文的论述 把中心点的偏移加上去
        # 并且很重要的一点, 我需要的图像一定要在feature map上出现, 而这个xy_grid 矩阵给每一个位置的索引直接变成了对等的数值
        # 通过相加以后就是说让网络去学习到可以允许偏移, 算出偏移量, 但是不能超过这个feature map 的边框
        pred_xy = (tf.sigmoid(conv_raw_dxdy) + xy_grid) * stride;
        # anchors 是一个三行两列的数组, 意义在于表示对应尺度下的3种bbox的边长的比值
        # 这里的长度的预测计算公式也是采用论文中提到的运用的e 的指数来计算, 结果更加稳定
        # 这里 tf.exp(conv_raw_dwdh) 是对矩阵中的每一个单独的元素进行计算维度不变
        pred_wh = (tf.exp(conv_raw_dwdh) * anchors) * stride;
        pred_xywh = tf.concat([pred_xy, pred_wh], axis=-1);

        pred_conf = tf.sigmoid(conv_raw_conf)
        pred_prob = tf.sigmoid(conv_raw_prob)

        return tf.concat([pred_xywh, pred_conf, pred_prob], axis=-1)

    def focal(self, target, actual, alpha=1, gamma=2):
        focal_loss = alpha * tf.pow(tf.abs(target - actual), gamma)
        return focal_loss

    def bbox_giou(self, boxes1, boxes2):

        # boxes1 = pred_xywh, boxes2 = label_xywh;
        # 这里的boxes1和boxes2 的处理功能是计算出标定框的起始点和结束点, 就是xmin, ymin; xmax, ymax;
        boxes1 = tf.concat([boxes1[..., :2] - boxes1[..., 2:] * 0.5,
                            boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1);
        boxes2 = tf.concat([boxes2[..., :2] - boxes2[..., 2:] * 0.5,
                            boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1);
        # 这里 boxes1 和 boxes2 的处理作用是对各自的起始点和结束点做一个比较, 确认一下真正的起始点和结束点
        boxes1 = tf.concat([tf.minimum(boxes1[..., :2], boxes1[..., 2:]),
                            tf.maximum(boxes1[..., :2], boxes1[..., 2:])], axis=-1);
        boxes2 = tf.concat([tf.minimum(boxes2[..., :2], boxes2[..., 2:]),
                            tf.maximum(boxes2[..., :2], boxes2[..., 2:])], axis=-1);
        # 这里是把 xmax - xmin, ymax - ymin, 相当于计算两个框的各自面积
        boxes1_area = (boxes1[..., 2] - boxes1[..., 0]) * (boxes1[..., 3] - boxes1[..., 1]);
        boxes2_area = (boxes2[..., 2] - boxes2[..., 0]) * (boxes2[..., 3] - boxes2[..., 1]);
        # 这里的处理作用, 首先boxes1 和 boxes2 各自的 前2个位置是xmin, ymin
        # 在xmin, ymin 中找最大值, 并在后两位（xmax, ymax）中找最小值
        # 这是为后面算2个标定框的交集的这个框的坐标做准备
        left_up = tf.maximum(boxes1[..., :2], boxes2[..., :2]);
        # 这个是结束点
        right_down = tf.minimum(boxes1[..., 2:], boxes2[..., 2:]);
        # 这里的作用是 right_down 一定要比 left_up 大
        # 即如果结束点小于起始点, 那就意味着并没有交集
        inter_section = tf.maximum(right_down - left_up, 0.0);
        # 这里的 数组的第一个数是图像中的x, 即行数的增加方向
        # 这里的 数组的第一个数是图像中的y, 即列数的增加方向
        # 这里用x * y 是为了计算这个交集的面积
        inter_area = inter_section[..., 0] * inter_section[..., 1];
        # 两个有交集的bbox 标定框各自的面积相加 减去交集的面积正好是他们的并集的面积
        union_area = boxes1_area + boxes2_area - inter_area;
        # 交集除以并集, 这个正好是iou 的定义
        iou = inter_area / union_area;
        # 这里是对boxes1 和boxes2 的xmin, ymin 进行比较选出最小
        # 请注意, 这里和之前的 left_up 变量处理的是不同的, 这里是在两个Box框的起始点之间找最小
        # 后面的enclose_right_down 和之前的 right_down 也是不同的, 它是在结束点之间找最大;
        # 那就是说这里是要算出一个整个并集中（bboxes1 和bboxes2 的联合的区域内）的起始点和结束点, 一个大的标定框
        enclose_left_up = tf.minimum(boxes1[..., :2], boxes2[..., :2]);
        # 这里是对boxes1 和boxes2 的xmax, ymax 进行比较选出最大
        enclose_right_down = tf.maximum(boxes1[..., 2:], boxes2[..., 2:]);
        # 这里是为了做个预处理, 确定最大标定框的存在, 即之前的坐标计算没有错误, 以防BUG出现
        enclose = tf.maximum(enclose_right_down - enclose_left_up, 0.0);
        # 计算出这个并集的大的标定框的面积
        enclose_area = enclose[..., 0] * enclose[..., 1];
        # 这里的 enclose_area - union_area 是指用大的标定框面积减去两个框并集的面积
        # 本身IOU 越大就是意味着重合度越高, 然后还要减去大框中除去并集面积以后的空白部分
        # 考虑这个空白部分那就意味着对于即使 IOU数值相同, 但是大框中留白面积不同也有区别
        # 比如如果两个框大小一样, 有交集, 且两个框在ymin, ymax 都相等, 那意味着大框中除了并联的面积并没有其它的留白部分
        # 而如果不相等那就会有留白部分, 意味着更倾向于它们相交时候的方位, 假设给方向分为上下左右外加四个对角线方向的话
        # 明显对角线方向的GIOU 会比其他方向的值要小
        giou = iou - 1.0 * (enclose_area - union_area) / enclose_area;
        # 经过多方查找, 这里的giou 的理论是2019.2月份的论文, 用来代替原先的坐标值的平方和loss
        return giou

    def bbox_iou(self, boxes1, boxes2):

        boxes1_area = boxes1[..., 2] * boxes1[..., 3];
        boxes2_area = boxes2[..., 2] * boxes2[..., 3];# 长和宽相乘是面积

        boxes1 = tf.concat([boxes1[..., :2] - boxes1[..., 2:] * 0.5,
                            boxes1[..., :2] + boxes1[..., 2:] * 0.5], axis=-1)
        boxes2 = tf.concat([boxes2[..., :2] - boxes2[..., 2:] * 0.5,
                            boxes2[..., :2] + boxes2[..., 2:] * 0.5], axis=-1)

        left_up = tf.maximum(boxes1[..., :2], boxes2[..., :2])
        right_down = tf.minimum(boxes1[..., 2:], boxes2[..., 2:])

        inter_section = tf.maximum(right_down - left_up, 0.0)
        inter_area = inter_section[..., 0] * inter_section[..., 1]
        union_area = boxes1_area + boxes2_area - inter_area
        iou = 1.0 * inter_area / union_area

        return iou

    def loss_layer(self, conv, pred, label, bboxes, anchors, stride):

        # self.conv_lbbox, self.conv_mbbox, self.conv_sbbox = self.__build_nework(input_data)
        # 这里的conv 就是上面写道的conv3个大小的bbox中的其中一个
        conv_shape  = tf.shape(conv)
        batch_size  = conv_shape[0]
        output_size = conv_shape[1]
        input_size  = stride * output_size
        conv = tf.reshape(conv, (batch_size, output_size, output_size,
                                 self.anchor_per_scale, 5 + self.num_class))
        conv_raw_conf = conv[:, :, :, :, 4:5]
        conv_raw_prob = conv[:, :, :, :, 5:]

        pred_xywh     = pred[:, :, :, :, 0:4]
        pred_conf     = pred[:, :, :, :, 4:5]

        label_xywh    = label[:, :, :, :, 0:4]
        respond_bbox  = label[:, :, :, :, 4:5]
        label_prob    = label[:, :, :, :, 5:]

        giou = tf.expand_dims(self.bbox_giou(pred_xywh, label_xywh), axis=-1);
        # 把图像大小的数据转换成浮点型
        input_size = tf.cast(input_size, tf.float32);

        # label_xywh[:, :, :, :, 2:3] * label_xywh[:, :, :, :, 3:4] / (input_size ** 2)
        # 这个式子实现了真实的标定框相对于整个图像的面积之比
        bbox_loss_scale = 2.0 - 1.0 * label_xywh[:, :, :, :, 2:3] * label_xywh[:, :, :, :, 3:4] / (input_size ** 2);
        # 结合上一行的式子, 感觉到bbox_loss_scale 更像一个比例系数
        # 假设giou 的数值没有变化, 但是标定框相对于整个原图占的比例很小, 那么这个时候bbox_loss_scale 能起到放大这个giou的差值
        giou_loss = respond_bbox * bbox_loss_scale * (1 - giou);

        iou = self.bbox_iou(pred_xywh[:, :, :, :, np.newaxis, :], bboxes[:, np.newaxis, np.newaxis, np.newaxis, :, :])
        max_iou = tf.expand_dims(tf.reduce_max(iou, axis=-1), axis=-1);
        # 这里设置iou_loss_thresh = 0.5, 如果最大IOU 小于0.5才是True, 这里直接把bool 值变成了浮点型
        # 在dataset 的处理中, 只有大于这个阈值的, label数组才会把对应的值设为1, 其余皆为0
        # respond_bbox 表示的是该位置是否有object, 如果有对象那 respond_bgd 是为 0;
        # 这里要设置条件是因为不设置会把所有 max_iou 全部算到 不含对象中, 我们只需要把最大的iou 中小于阈值的才算作不含对象
        respond_bgd = (1.0 - respond_bbox) * tf.cast( max_iou < self.iou_loss_thresh, tf.float32 );
        # 预测的置信度与真值 的差的平方
        conf_focal = self.focal(respond_bbox, pred_conf);
        # 因为fetaure map 中肯定不含有对象的点要比有对象的点多, 所以如果不计算背景的Loss 那么大量的负样本会把模型带偏
        conf_loss = conf_focal * (
                # 含object 的box 的confidence 预测
                respond_bbox * tf.nn.sigmoid_cross_entropy_with_logits(labels=respond_bbox, logits=conv_raw_conf)
                +
                # 不含object 的box 的confidence 预测
                respond_bgd * tf.nn.sigmoid_cross_entropy_with_logits(labels=respond_bbox, logits=conv_raw_conf)
        )
        # 预测类别的交叉熵损失
        prob_loss = respond_bbox * tf.nn.sigmoid_cross_entropy_with_logits(labels=label_prob, logits=conv_raw_prob)

        giou_loss = tf.reduce_mean(tf.reduce_sum(giou_loss, axis=[1,2,3,4]))
        conf_loss = tf.reduce_mean(tf.reduce_sum(conf_loss, axis=[1,2,3,4]))
        prob_loss = tf.reduce_mean(tf.reduce_sum(prob_loss, axis=[1,2,3,4]))

        return giou_loss, conf_loss, prob_loss



    def compute_loss(self, label_sbbox, label_mbbox, label_lbbox, true_sbbox, true_mbbox, true_lbbox):

        with tf.name_scope('smaller_box_loss'):
            loss_sbbox = self.loss_layer(self.conv_sbbox, self.pred_sbbox, label_sbbox, true_sbbox,
                                         anchors = self.anchors[0], stride = self.strides[0])

        with tf.name_scope('medium_box_loss'):
            loss_mbbox = self.loss_layer(self.conv_mbbox, self.pred_mbbox, label_mbbox, true_mbbox,
                                         anchors = self.anchors[1], stride = self.strides[1])

        with tf.name_scope('bigger_box_loss'):
            loss_lbbox = self.loss_layer(self.conv_lbbox, self.pred_lbbox, label_lbbox, true_lbbox,
                                         anchors = self.anchors[2], stride = self.strides[2])

        with tf.name_scope('giou_loss'):
            giou_loss = loss_sbbox[0] + loss_mbbox[0] + loss_lbbox[0]

        with tf.name_scope('conf_loss'):
            conf_loss = loss_sbbox[1] + loss_mbbox[1] + loss_lbbox[1]

        with tf.name_scope('prob_loss'):
            prob_loss = loss_sbbox[2] + loss_mbbox[2] + loss_lbbox[2]

        return giou_loss, conf_loss, prob_loss


